/*
 * Copyright (C) 2011 JiangHongTiao <jjurco.sk_gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package sk.lieskove.jianghongtiao.websearch.service.searchengine;

import com.google.code.bing.search.client.BingSearchClient;
import com.google.code.bing.search.client.BingSearchClient.SearchRequestBuilder;
import com.google.code.bing.search.client.BingSearchServiceClientFactory;
import com.google.code.bing.search.schema.AdultOption;
import com.google.code.bing.search.schema.SearchOption;
import com.google.code.bing.search.schema.SearchResponse;
import com.google.code.bing.search.schema.SourceType;
import com.google.code.bing.search.schema.web.WebResult;
import com.google.code.bing.search.schema.web.WebSearchOption;
import com.jellymold.boss.WebSearch.WebSearchFilter;
import com.jellymold.boss.WebSearchResult;
import org.apache.log4j.Logger;
import sk.lieskove.jianghongtiao.common.enums.searchserviceenum.SearchServiceEnum;
import sk.lieskove.jianghongtiao.common.exceptions.HTTPResponseException;
import sk.lieskove.jianghongtiao.common.exceptions.InvalidPropertiesSettingsException;
import sk.lieskove.jianghongtiao.common.utils.PropertiesUtils;
import sk.lieskove.jianghongtiao.multithreaddownloader.MultiThreadDownloadManager;
import sk.lieskove.jianghongtiao.multithreaddownloader.MultiThreadDownloader;
import sk.lieskove.jianghongtiao.multithreaddownloader.document.RemoteDocument;
import sk.lieskove.jianghongtiao.websearch.api.google.search.*;
import sk.lieskove.jianghongtiao.websearch.service.SearchQuery;
import sk.lieskove.jianghongtiao.websearch.service.SearchService;
import sk.lieskove.jianghongtiao.websearch.persistence.WebSearchResponse;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;

/**
 * Date of create: Jul 11, 2011
 *
 * @author JiangHongTiao <jjurco.sk_gmail.com>
 * @version 2011.0711
 */
public class GYBWebSearch implements SearchService {

    private static String bingAppId = null;
    private static BingSearchClient bingSearch = null;
    private static com.jellymold.boss.WebSearch yahooSearch = null;
    private static GoogleWebSearch googleWebSearch = new GoogleWebSearchImpl();
    private static PropertiesUtils pu = new PropertiesUtils(GYBWebSearch.class);
    private static Logger log = Logger.getLogger(GYBWebSearch.class);
    private Boolean googleAllowed;
    private Boolean yahooAllowed;
    private Boolean bingAllowed;
    private Integer googleDefaultLimit = 64;
    private Integer googleHardLimit = 64;
    private Integer yahooDefaultLimit = 50;
    private Integer yahooHardLimit = 50;
    private Integer bingDefaultLimit = 50;
    private Integer bingHardLimit = 50;
    private MultiThreadDownloadManager downloader = MultiThreadDownloader.
            getInstance();
    public static final SearchServiceEnum[] SUPPORTED_SERVICES = {
        SearchServiceEnum.WEBSEARCH_BING,
        SearchServiceEnum.WEBSEARCH_GOOGLE,
        SearchServiceEnum.WEBSEARCH_YAHOO
    };

    public GYBWebSearch() throws InvalidPropertiesSettingsException {
        super();
        googleAllowed = Boolean.valueOf(pu.getProperty(
                "google-search-enabled"));
        yahooAllowed = Boolean.valueOf(pu.getProperty(
                "yahoo-search-enabled"));
        bingAllowed = Boolean.valueOf(pu.getProperty(
                "bing-search-enabled"));
        if (!(googleAllowed || yahooAllowed || bingAllowed)) {
            log.warn("No search service (google/yahoo/bing) is alloved in"
                    + " settings file. Cannot search!");
        }
        if (googleAllowed) {
            createGoogleSearch();
        }
        if (yahooAllowed) {
            createYahooSearch();
        }
        if (bingAllowed) {
            createBingSearch();
        }
    }

    private void readLimit(Integer hardLimit, Integer defaultLimit,
            String service) {
        String hard = pu.getProperty(service + "-hard-results-limit");
        if (hard != null) {
            hardLimit = Integer.parseInt(hard);
        }
        String defaultLim = pu.getProperty(service + "-default-results-limit");
        if (defaultLim != null) {
            defaultLimit = Integer.parseInt(defaultLim);
        }

    }

    private void createBingSearch() throws InvalidPropertiesSettingsException {
        BingSearchServiceClientFactory factory = BingSearchServiceClientFactory.
                newInstance();
        bingSearch = factory.createBingSearchClient();
        bingAppId = pu.getProperty("bing-service-api-key", null);
        readLimit(bingHardLimit, bingDefaultLimit, "bing");
        if (bingAppId == null) {
            log.error(
                    "Bing AppId was not found in settings files. For more "
                    + "information visit url: http://www.bing.com/developers/createapp.aspx");
            throw new InvalidPropertiesSettingsException("bing-service-api-key",
                    this.getClass(), "");
        }
    }

    private void createGoogleSearch() throws InvalidPropertiesSettingsException {
        String appKey = pu.getProperty("google-search-api-key", null);
        if (appKey == null) {
            log.error(
                    "Google search API key was not found in settings files. For more "
                    + "information visit url: http://code.google.com/apis/loader/signup.html");
            throw new InvalidPropertiesSettingsException("google-search-api-key",
                    this.getClass(), "");
        }
        readLimit(googleHardLimit, googleDefaultLimit, "google");
        googleWebSearch.withKey(appKey);
    }

    private void createYahooSearch() throws InvalidPropertiesSettingsException {
        String yahooAppKey = pu.getProperty("yahoo-api-customer-key", null);
        if (yahooAppKey == null) {
            log.error(
                    "Yahoo customer key key was not found in settings files. For more "
                    + "information visit url: https://developer.apps.yahoo.com/dashboard/createKey.html");
            throw new InvalidPropertiesSettingsException(
                    "yahoo-api-customer-key", this.getClass(), "");
        }
        readLimit(yahooHardLimit, yahooDefaultLimit, "yahoo");
        yahooSearch = new com.jellymold.boss.WebSearch();
        yahooSearch.setAppKey(yahooAppKey);
    }

    @Override
    public List<WebSearchResponse> search(SearchQuery query, int numberOfResults,
            SearchServiceEnum[] services) {
        List<WebSearchResponse> result = new ArrayList<WebSearchResponse>();
        String searchQuery = query.getFullQuery();
        for (SearchServiceEnum searchService : services) {
            switch (searchService) {
                case WEBSEARCH_BING:
                    if (bingAllowed) {
                        result.addAll(bingSearch(searchQuery, numberOfResults));
                    }
                    break;
                case WEBSEARCH_GOOGLE:
                    if (googleAllowed) {
                        result.addAll(googleSearch(searchQuery, numberOfResults));
                    }
                    break;
                case WEBSEARCH_YAHOO:
                    if (yahooAllowed) {
                        result.addAll(yahooSearch(searchQuery, numberOfResults));
                    }
                    break;
            }
        }
        return getSearchResults(result, searchQuery);
    }

    @Override
    public List<WebSearchResponse> search(SearchQuery query,
            SearchServiceEnum[] services) {
        List<WebSearchResponse> result = new ArrayList<WebSearchResponse>();
        String searchQuery = query.getFullQuery();
        int bLimit = 50;
        int gLimit = 64;
        int yLimit = 50;
        //bing
        if (bingDefaultLimit != null) {
            if (bingHardLimit != null) {
                if (bingDefaultLimit <= bingHardLimit)
                     bLimit = bingDefaultLimit;
                else bLimit = bingHardLimit;
            } else {
                if (bingDefaultLimit <= 50) bLimit = bingDefaultLimit;
            }
        } else {
            if (bingHardLimit != null) bLimit = bingHardLimit;
        }
        //google
        if (googleDefaultLimit != null) {
            if (googleHardLimit != null) {
                if (googleDefaultLimit <= googleHardLimit)
                     gLimit = googleDefaultLimit;
                else gLimit = googleHardLimit;
            } else {
                if (googleDefaultLimit <= 50) gLimit = googleDefaultLimit;
            }
        } else {
            if (googleHardLimit != null) gLimit = googleHardLimit;
        }
        //yahoo
        if (yahooDefaultLimit != null) {
            if (yahooHardLimit != null) {
                if (yahooDefaultLimit <= yahooHardLimit)
                     yLimit = yahooDefaultLimit;
                else yLimit = yahooHardLimit;
            } else {
                if (yahooDefaultLimit <= 50) yLimit = yahooDefaultLimit;
            }
        } else {
            if (yahooHardLimit != null) yLimit = yahooHardLimit;
        }

        for (SearchServiceEnum searchService : services) {
            switch (searchService) {
                case WEBSEARCH_BING:
                    if (bingAllowed) {
                        result.addAll(bingSearch(searchQuery, bLimit));
                    }
                    break;
                case WEBSEARCH_GOOGLE:
                    if (googleAllowed) {
                        result.addAll(googleSearch(searchQuery, gLimit));
                    }
                    break;
                case WEBSEARCH_YAHOO:
                    if (yahooAllowed) {
                        result.addAll(yahooSearch(searchQuery, yLimit));
                    }
                    break;
            }
        }
        return getSearchResults(result, searchQuery);
    }

    /**
     * Search for Query through search engines. Search will be performed through
     * search engines allowed in the settings file. There are three search engines
     * now for search: Google, Yahoo, Bing. Default number of results is 50
     * 
     * @param query search for this query and get results
     * @return list of downloaded documents found 
     */
    @Override
    public List<WebSearchResponse> searchAll(SearchQuery query) {
        return search(query, SUPPORTED_SERVICES);
    }

    /**
     * 
     * @param query
     * @param numberOfResults
     * @return 
     */
    public List<WebSearchResponse> searchAll(SearchQuery query,
            int numberOfResults) {
        return search(query, numberOfResults, SUPPORTED_SERVICES);
    }

    public List<WebSearchResponse> getSearchResults(
            List<WebSearchResponse> results, String searchQuery) {
        for (WebSearchResponse result : results) {
            RemoteDocument download = downloader.getDownload(result.getUuidObj());
            if (download != null) {
                result.setRemoteDocument(download);
                result.setSearchQuery(searchQuery);
            }
        }
        return results;
    }

    public List<WebSearchResponse> googleSearch(String query,
            int numberOfResults) {
        GWSLanguage lang = GWSLanguage.fromCode(pu.getProperty(
                "google-search-language"));
        if (lang != null) {
            googleWebSearch.withLanguage(lang);
        }

        googleWebSearch.withQuery(query);
        List<WebSearchResponse> result = new ArrayList<WebSearchResponse>();
        try {
            GWSResponse results = googleWebSearch.search(numberOfResults);
            if (results != null) {
                int order = 1;
                for (Iterator<GWSResult> it = results.getResponseData().
                        getResults().
                        iterator(); it.hasNext();) {
                    GWSResult searchResult = it.next();
                    result.add(new WebSearchResponse(downloader.addLink(
                            new URL(searchResult.getUnescapedUrl())),
                            searchResult.getUnescapedUrl(),
                            searchResult.getTitle(), searchResult.getContent(),
                            SearchServiceEnum.WEBSEARCH_GOOGLE.value(), order));
                    order++;
                }
            }
        } catch (HTTPResponseException e) {
            log.warn("Search through google failed!");
        } catch (MalformedURLException ex) {
            java.util.logging.Logger.getLogger(GYBWebSearch.class.getName()).
                    log(Level.SEVERE, null, ex);
        }
        return result;
    }

    public List<WebSearchResponse> yahooSearch(String query, int numberOfResults) {
        List<WebSearchResponse> result = new ArrayList<WebSearchResponse>();
        Set<WebSearchFilter> filter = new HashSet<WebSearchFilter>();
        filter.add(WebSearchFilter.PORN);
        yahooSearch.setFilters(filter);
        yahooSearch.search(query);
        Date date = new Date();
        List<WebSearchResult> results = yahooSearch.getResults();
        int order = 1;
        while (order < numberOfResults) {
            for (Iterator<WebSearchResult> it = results.iterator(); it.hasNext();) {
                try {
                    WebSearchResult webSearchResult = it.next();
                    result.add(new WebSearchResponse(
                            downloader.addLink(
                            new URL(webSearchResult.getUrl())),
                            webSearchResult.getUrl(),
                            webSearchResult.getTitle(), webSearchResult.
                            getDescription(),
                            SearchServiceEnum.WEBSEARCH_YAHOO.value(), order));
                    order++;
                } catch (MalformedURLException ex) {
                    java.util.logging.Logger.getLogger(GYBWebSearch.class.
                            getName()).
                            log(Level.SEVERE, null, ex);
                }
            }
            yahooSearch.getNextPage();
        }
        return result;
    }

    public List<WebSearchResponse> bingSearch(String query, int numberOfResults) {
        List<WebSearchResponse> result = new ArrayList<WebSearchResponse>();
        SearchRequestBuilder builder = bingSearch.newSearchRequestBuilder();
        builder.withAppId(bingAppId);
        builder.withQuery(query);
        builder.withSourceType(SourceType.WEB);
        builder.withVersion("2.0");
        builder.withMarket(pu.getProperty("bing-search-language"));
        builder.withAdultOption(AdultOption.MODERATE);
        builder.withSearchOption(SearchOption.ENABLE_HIGHLIGHTING);

        builder.withWebRequestCount(new Long(numberOfResults));
        builder.withWebRequestOffset(0L);
        builder.withWebRequestSearchOption(
                WebSearchOption.DISABLE_HOST_COLLAPSING);
        builder.withWebRequestSearchOption(
                WebSearchOption.DISABLE_QUERY_ALTERATIONS);

        SearchResponse response = bingSearch.search(builder.getResult());
        Date date = new Date();
        int order = 1;
        if (response.getWeb() != null) {
            List<WebResult> results = response.getWeb().getResults();
            if (results != null) {
                for (WebResult res : results) {
                    try {
                        result.add(new WebSearchResponse(downloader.addLink(new URL(res.
                                getUrl())), res.getUrl(), res.getTitle(), res.
                                getDescription(),
                                SearchServiceEnum.WEBSEARCH_BING.value(),
                                order));
                    } catch (MalformedURLException ex) {
                        log.info("Malformed URL, cannot download resource: "
                                + res.getUrl());
                    }
                    order++;
                }
            }
        }

        return result;
    }
}